/*!
 * html-replacer.js v0.1.1
 * @cj_zheng
 * Released under the MIT License.
 */
var htmlReplacer = (function (exports) {
  'use strict';

  function ownKeys(object, enumerableOnly) {
    var keys = Object.keys(object);

    if (Object.getOwnPropertySymbols) {
      var symbols = Object.getOwnPropertySymbols(object);
      enumerableOnly && (symbols = symbols.filter(function (sym) {
        return Object.getOwnPropertyDescriptor(object, sym).enumerable;
      })), keys.push.apply(keys, symbols);
    }

    return keys;
  }

  function _objectSpread2(target) {
    for (var i = 1; i < arguments.length; i++) {
      var source = null != arguments[i] ? arguments[i] : {};
      i % 2 ? ownKeys(Object(source), !0).forEach(function (key) {
        _defineProperty(target, key, source[key]);
      }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) {
        Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
      });
    }

    return target;
  }

  function _defineProperty(obj, key, value) {
    if (key in obj) {
      Object.defineProperty(obj, key, {
        value: value,
        enumerable: true,
        configurable: true,
        writable: true
      });
    } else {
      obj[key] = value;
    }

    return obj;
  }

  function _slicedToArray(arr, i) {
    return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
  }

  function _toConsumableArray(arr) {
    return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
  }

  function _arrayWithoutHoles(arr) {
    if (Array.isArray(arr)) return _arrayLikeToArray(arr);
  }

  function _arrayWithHoles(arr) {
    if (Array.isArray(arr)) return arr;
  }

  function _iterableToArray(iter) {
    if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
  }

  function _iterableToArrayLimit(arr, i) {
    var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];

    if (_i == null) return;
    var _arr = [];
    var _n = true;
    var _d = false;

    var _s, _e;

    try {
      for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) {
        _arr.push(_s.value);

        if (i && _arr.length === i) break;
      }
    } catch (err) {
      _d = true;
      _e = err;
    } finally {
      try {
        if (!_n && _i["return"] != null) _i["return"]();
      } finally {
        if (_d) throw _e;
      }
    }

    return _arr;
  }

  function _unsupportedIterableToArray(o, minLen) {
    if (!o) return;
    if (typeof o === "string") return _arrayLikeToArray(o, minLen);
    var n = Object.prototype.toString.call(o).slice(8, -1);
    if (n === "Object" && o.constructor) n = o.constructor.name;
    if (n === "Map" || n === "Set") return Array.from(o);
    if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
  }

  function _arrayLikeToArray(arr, len) {
    if (len == null || len > arr.length) len = arr.length;

    for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];

    return arr2;
  }

  function _nonIterableSpread() {
    throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
  }

  function _nonIterableRest() {
    throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
  }

  function _createForOfIteratorHelper(o, allowArrayLike) {
    var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];

    if (!it) {
      if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
        if (it) o = it;
        var i = 0;

        var F = function () {};

        return {
          s: F,
          n: function () {
            if (i >= o.length) return {
              done: true
            };
            return {
              done: false,
              value: o[i++]
            };
          },
          e: function (e) {
            throw e;
          },
          f: F
        };
      }

      throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
    }

    var normalCompletion = true,
        didErr = false,
        err;
    return {
      s: function () {
        it = it.call(o);
      },
      n: function () {
        var step = it.next();
        normalCompletion = step.done;
        return step;
      },
      e: function (e) {
        didErr = true;
        err = e;
      },
      f: function () {
        try {
          if (!normalCompletion && it.return != null) it.return();
        } finally {
          if (didErr) throw err;
        }
      }
    };
  }

  /**
   * 替换非标签的<,>符号
   * @author zhengchj
   * @param targetStr
   * @return {string}
   */
  /**
   * 替换所有<,>符号
   * @author zhengchj
   * @param targetStr
   * @return {string}
   */

  var replaceAllArrow = function replaceAllArrow(targetStr) {
    return targetStr.replace(/</gi, '&lt;').replace(/>/gi, '&gt;');
  };
  /**
   * 查找两个节点最邻近的共同父节点
   * @author zhengchj
   * @param node1
   * @param node2
   * @return {Node}
   */

  var queryCommonParentNode = function queryCommonParentNode(node1, node2) {
    if (node1.contains(node2)) {
      return node1;
    } else if (node2.contains(node1)) {
      return node2;
    } else {
      return queryCommonParentNode(node1.parentNode, node2);
    }
  };
  /**
   * dom树抽出文本节点转数组
   * @author zhengchj
   * @param root
   * @return {Array}
   */

  var flattenTree = function flattenTree(root) {
    var array = [];

    if (!root.hasChildNodes()) {
      array.push(root);
      return array;
    }

    var nodes = root.childNodes;

    for (var i = 0, len = nodes.length; i < len; i++) {
      Array.prototype.push.apply(array, flattenTree(nodes[i]));
    }

    return array;
  };
  /**
   * 对象封装成集合
   * @author zhengchj
   * @param objects
   * @returns {[]}
   */

  var toArray = function toArray(objects) {
    return Array.isArray(objects) ? objects : [objects];
  };
  /**
   * 生成uuid
   * @return {string}
   */

  var createUUID = function createUUID() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
      var r = Math.random() * 16 | 0,
          v = c == 'x' ? r : r & 0x3 | 0x8;
      return v.toString(16);
    });
  };
  /**
   * 正则特殊符号转义   参考lodash  escapeRegExp方法
   * @param string
   * @return {string}
   */

  var escapeRegExp = function escapeRegExp(string) {
    var reRegExpChar = /[\\^$.*+()[\]{}|?]/g;
    return reRegExpChar.test(string) ? string.replace(reRegExpChar, '\\$&') : string;
  };
  /**
   * // This code was found: https://awik.io/determine-color-bright-dark-using-javascript/
   * 判断颜色是否为浅色
   * @param color
   * @return {boolean}
   */

  var isLight = function isLight(color) {
    // Variables for red, green, blue values
    var r, g, b, hsp; // Check the format of the color, HEX or RGB?

    if (color.match(/^rgb/)) {
      // If RGB --> store the red, green, blue values in separate variables
      color = color.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/);
      r = color[1];
      g = color[2];
      b = color[3];
    } else {
      // If hex --> Convert it to RGB: http://gist.github.com/983661
      color = +('0x' + color.slice(1).replace(color.length < 5 && /./g, '$&$&'));
      r = color >> 16;
      g = color >> 8 & 255;
      b = color & 255;
    } // HSP (Highly Sensitive Poo) equation from http://alienryderflex.com/hsp.html


    hsp = Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b)); // Using the HSP value, determine whether the color is light or dark

    return hsp > 127.5;
  };

  /**
   * 生成正则规则
   * @author zhengchj
   * @param highlighter  高亮关键词对象
   * @returns {*}        返回正则规则
   * @private
   */

  var _createRule = function _createRule(highlighter) {
    var word = highlighter.word; //替换高亮词中的<,>

    word = replaceAllArrow(word); //正则特殊字符转义

    word = escapeRegExp(word); // 拆解高亮关键词

    word = _spreadWord(word);
    return word;
  };
  /**
   * 展开高亮关键词，每个字符前后加正则规则
   * @example 手机 -> (<span class="hl[^"]*"[^<>]+>手</span>|手)(<span class="hl[^"]*"[^<>]+>机</span>|机)
   * @param word   高亮关键词
   * @returns string
   */


  var _spreadWord = function _spreadWord(word) {
    var chars = word.match(/(&lt;|&gt;|\\[\\^$.*+?()[\]{}|]|.)/gi);
    var newWord = '';

    var _iterator = _createForOfIteratorHelper(chars),
        _step;

    try {
      for (_iterator.s(); !(_step = _iterator.n()).done;) {
        var char = _step.value;
        newWord += _buildCharRegExp(char);
      }
    } catch (err) {
      _iterator.e(err);
    } finally {
      _iterator.f();
    }

    return newWord;
  };
  /**
   * 为字符构建正则表达式
   * @param char
   * @returns string
   */


  var _buildCharRegExp = function _buildCharRegExp(char) {
    return "((<span class=\"hl[^\"]*\"[^<>]+>)+".concat(char, "(</span>)+|").concat(char, ")");
  };
  /**
   *
   * 原则：后端传的文本如果含有左右尖括号，需要后端转义，标签的左右尖括号不用转义
   *
   * 执行高亮操作
   * @author zhengchj
   * @param targetStr     目标字符串
   * @param highlighters  高亮关键词集合或对象
   * @return string       执行高亮后的字符串
   */


  var highlight = function highlight(targetStr, highlighters) {
    if (!targetStr) {
      return '';
    }

    var str = targetStr;
    toArray(highlighters).forEach(function (item) {
      var word = item.word,
          color = item.color;

      if (!word) {
        return;
      }

      var rule = _createRule(item); //高亮关键词匹配规则,标签中的内容不可匹配


      var regExp = new RegExp("((".concat(rule, ")(?![^<>]*>))"), 'gi'); //判断是否浅色

      var fontColor = isLight(color) ? '#000' : '#fff'; //高亮关键词加高亮标签

      str = str.replace(regExp, function (word) {
        var i = 1;
        return word.replace(/((&lt;|&gt;|[^<>])(?![^<>]*>))/gi, function (w) {
          var result = '';

          if (i === 1) {
            result = "<span class=\"hl hl-start\" style=\"background-color: ".concat(color, ";color: ").concat(fontColor, ";\">").concat(w, "</span>");
          } else {
            result = "<span class=\"hl\" style=\"background-color: ".concat(color, ";color: ").concat(fontColor, ";\">").concat(w, "</span>");
          }

          i++;
          return result;
        });
      });
    });
    return str;
  };

  /**
   * 获取高亮节点在容器内的相对高度
   * @param target
   * @param container
   * @return {Number|number}  相对高度
   * @private
   */
  var _getTop = function _getTop(target, container) {
    var top = target.offsetTop;
    var parent = target.offsetParent;

    if (parent && container !== parent) {
      top += _getTop(parent, container);
    }

    return top;
  };
  /**
   * 获取百分比
   * @param dividend
   * @param divisor
   * @return {*}
   * @private
   */


  var _getPercentage = function _getPercentage(dividend, divisor) {
    return divisor ? (dividend / divisor * 100).toFixed(1) + '%' : 0;
  };
  /**
   * 执行高密操作
   * @param container   外层容器
   * @param className   需要高密的样式名
   * @return {Object}      高密Object<相对高度百分比, 颜色集合>
   */


  var highdense = function highdense(container) {
    var className = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "hl-start";
    var nodes = container.querySelectorAll(".".concat(className));
    var map = new Map();
    var height = container.offsetHeight;
    nodes.forEach(function (node) {
      var color = node['style']['background-color'];

      if (!color) {
        return;
      }

      var top = _getTop(node, container);

      var percentage = _getPercentage(top, height);

      if (!map.has(percentage)) {
        map.set(percentage, new Set());
      }

      map.get(percentage).add(color);
    });
    var result = {};

    var _iterator = _createForOfIteratorHelper(map),
        _step;

    try {
      for (_iterator.s(); !(_step = _iterator.n()).done;) {
        var _step$value = _slicedToArray(_step.value, 2),
            percentage = _step$value[0],
            set = _step$value[1];

        result[percentage] = _toConsumableArray(set);
      }
    } catch (err) {
      _iterator.e(err);
    } finally {
      _iterator.f();
    }

    return result;
  };

  function styleInject(css, ref) {
    if (ref === void 0) ref = {};
    var insertAt = ref.insertAt;

    if (!css || typeof document === 'undefined') {
      return;
    }

    var head = document.head || document.getElementsByTagName('head')[0];
    var style = document.createElement('style');
    style.type = 'text/css';

    if (insertAt === 'top') {
      if (head.firstChild) {
        head.insertBefore(style, head.firstChild);
      } else {
        head.appendChild(style);
      }
    } else {
      head.appendChild(style);
    }

    if (style.styleSheet) {
      style.styleSheet.cssText = css;
    } else {
      style.appendChild(document.createTextNode(css));
    }
  }

  var css_248z = ".hr-mark{background-color:#ff9;cursor:pointer;user-select:none}.hr-mark-hover{background-color:#f7aaaa}";
  styleInject(css_248z);

  /**
   * 获取指定元素
   * @author zhengchj
   * @param root       根
   * @param tagName    标签名称
   * @param tagIndex   下标
   * @private
   */

  var _getElement = function _getElement(root, _ref) {
    var tagName = _ref.tagName,
        tagIndex = _ref.tagIndex;
    return tagIndex === -1 ? root : root.getElementsByTagName(tagName)[tagIndex];
  };
  /**
   * 获取指定节点
   * @author zhengchj
   * @param pElement      父元素
   * @param offset        相对于父元素的文本偏移量
   * @param returnFirst   是否返回splitText之后的第一个节点
   * @private
   */


  var _getNode = function _getNode(pElement, offset, returnFirst) {
    var nodeStack = [pElement];
    var curNode;
    var realOffset = 0,
        curOffset = 0;

    while (curNode = nodeStack.pop()) {
      var nodes = curNode.childNodes;

      for (var length = nodes.length, i = length - 1; i >= 0; i--) {
        nodeStack.push(nodes[i]);
      } //当是文本节点的时候


      if (curNode.nodeType === 3) {
        realOffset = offset - curOffset;
        curOffset += curNode.textContent.length;

        if (curOffset >= offset) {
          break;
        }
      }
    }

    curNode.splitText(realOffset);
    return returnFirst ? curNode : curNode.nextSibling;
  };
  /**
   * 获取起始节点结束节点区间内的所有文本节点
   * @author zhengchj
   * @param startNode     起始节点
   * @param endNode       结束节点
   * @return {Array}
   * @private
   */


  var _getSelectedNodes = function _getSelectedNodes(startNode, endNode) {
    var parentNode = queryCommonParentNode(startNode, endNode);
    var flag = 0; //0:首尾区间之前    1:首尾区间内      2:首尾区间之后

    var nodes = flattenTree(parentNode);
    return nodes.filter(function (item) {
      if (item === startNode) {
        flag = 1;
        return true;
      }

      if (item === endNode) {
        flag = 2;
        return true;
      }

      return flag === 1;
    });
  };

  var MARK_ATTR = "data-mark-id";
  var DEFAULT_OPTIONS = {
    className: 'hr-mark',
    hoverClassName: 'hr-mark-hover',
    mouseenter: function mouseenter(id) {},
    mouseleave: function mouseleave(id) {}
  };
  /**
   * 包裹器
   * @author zhengchj
   * @param root
   * @param nodes
   * @param options
   * @param id
   * @private
   */

  var _wrap = function _wrap(root, nodes, options, id) {
    // 跟设为相对定位，用于删除按钮的绝对定位
    root.style.position = 'relative';
    nodes.forEach(function (node) {
      var wrap = document.createElement('span');
      wrap.setAttribute(MARK_ATTR, id);
      var className = options.className,
          hoverClassName = options.hoverClassName,
          mouseenter = options.mouseenter,
          mouseleave = options.mouseleave;
      wrap.className = className;
      wrap.addEventListener('mouseenter', function (_) {
        mouseenter(id);
        document.querySelectorAll("[".concat(MARK_ATTR, "='").concat(id, "']")).forEach(function (el) {
          el.className = "".concat(className, " ").concat(hoverClassName);
        });
      });
      wrap.addEventListener('mouseleave', function (_) {
        mouseleave(id);
        document.querySelectorAll("[".concat(MARK_ATTR, "='").concat(id, "']")).forEach(function (dom) {
          dom.className = className;
        });
      });
      wrap.appendChild(node.cloneNode(false));
      node.parentNode.replaceChild(wrap, node);
    });
  };
  /**
   * 执行mark标记
   * @author zhengchj
   * @param root
   * @param markers     首尾位置信息对象或集合         example: {id: '', start: {tagName: 'p', tagIndex: 0, offset: 1}, end: {tagName: 'p', tagIndex: 0, offset: 9}}
   * @param options     配置项
   */


  var mark = function mark(root, markers, options) {
    if (!root) {
      return;
    }

    toArray(markers).forEach(function (marker) {
      var id = marker.id,
          start = marker.start,
          end = marker.end;

      var startParentElement = _getElement(root, start);

      var endParentElement = _getElement(root, end);

      var startNode = _getNode(startParentElement, start.offset, false);

      var endNode = _getNode(endParentElement, end.offset, true);

      var selectedNodes = _getSelectedNodes(startNode, endNode);

      var op = options ? _objectSpread2(_objectSpread2({}, DEFAULT_OPTIONS), options) : DEFAULT_OPTIONS;

      _wrap(root, selectedNodes, op, id);
    });
  };
  /**
   * 获取相对于root元素的element索引和标签名称
   * @author zhengchj
   * @param root
   * @param element
   * @return {{tagName: string, tagIndex: number}}
   * @private
   */

  var _getTag = function _getTag(root, element) {
    var tagName = element.tagName;
    var elements = root.getElementsByTagName(tagName);

    for (var i = 0, len = elements.length; i < len; i++) {
      if (element === elements[i]) {
        return {
          tagName: tagName,
          tagIndex: i
        };
      }
    } // -1表示根节点


    return {
      tagName: tagName,
      tagIndex: -1
    };
  };
  /**
   * 获取节点相对于父节点的偏移量
   * @author zhengchj
   * @param node         节点
   * @param offset       在当前节点中的偏移量
   * @return {number}
   * @private
   */


  var _getRealOffset = function _getRealOffset(node, offset) {
    var nodeStack = [node.parentElement];
    var curNode;
    var realOffset = offset;

    while (curNode = nodeStack.pop()) {
      var nodes = curNode.childNodes;

      for (var len = nodes.length, i = len - 1; i >= 0; i--) {
        nodeStack.push(nodes[i]);
      }

      if (curNode.nodeType === 3 && curNode !== node) {
        realOffset += curNode.textContent.length;
      } else if (curNode.nodeType === 3) {
        break;
      }
    }

    return realOffset;
  };
  /**
   * 生成首尾位置信息对象
   * @author zhengchj
   * @param root      根节点
   * @param range     Range对象
   * @return object          example: {id: '', start: {tagName: 'p', tagIndex: 0, offset: 1}, end: {tagName: 'p', tagIndex: 0, offset: 9}}
   */


  var createMarker = function createMarker(root, range) {
    if (!root) {
      return null;
    }

    var startContainer = range.startContainer,
        endContainer = range.endContainer,
        startOffset = range.startOffset,
        endOffset = range.endOffset;

    var startParentTag = _getTag(root, startContainer.parentElement);

    var endParentTag = _getTag(root, endContainer.parentElement);

    var startRealOffset = _getRealOffset(startContainer, startOffset);

    var endRealOffset = _getRealOffset(endContainer, endOffset);

    var id = createUUID();
    return {
      id: id,
      start: _objectSpread2({
        offset: startRealOffset
      }, startParentTag),
      end: _objectSpread2({
        offset: endRealOffset
      }, endParentTag)
    };
  };
  /**
   * 删除标记
   * @author zhengchj
   * @param id
   */

  var unmark = function unmark(id) {
    document.querySelectorAll("[".concat(MARK_ATTR, "='").concat(id, "']")).forEach(function (el) {
      //判断待删除节点前面的节点和后面的节点，如果是文本节点，则做整合
      var prevNode = el.previousSibling,
          nextNode = el.nextSibling;
      var prevText = '',
          nextText = '';

      if (prevNode && prevNode.nodeType === 3) {
        prevText = prevNode.textContent;
        el.parentNode.removeChild(prevNode);
      }

      if (nextNode && nextNode.nodeType === 3) {
        nextText = nextNode.textContent;
        el.parentNode.removeChild(nextNode);
      }

      var textNode = document.createTextNode(prevText + el.textContent + nextText);
      el.parentNode.replaceChild(textNode, el);
    });
  };

  var htmlReducer = /*#__PURE__*/Object.freeze({
    __proto__: null,
    highlight: highlight,
    highdense: highdense,
    mark: mark,
    createMarker: createMarker,
    unmark: unmark
  });

  exports.createMarker = createMarker;
  exports["default"] = htmlReducer;
  exports.highdense = highdense;
  exports.highlight = highlight;
  exports.mark = mark;
  exports.unmark = unmark;

  return exports;

})({});
